package com.youth.aop;

import com.alibaba.fastjson.JSON;
import com.youth.log.LogEntity;
import com.youth.mq.producer.RMQProducer;
import com.youth.result.ResultBean;
import com.youth.utils.CodeUtil;
import com.youth.utils.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.SocketException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName WebLogAop
 * @Description: TODO log日志切面类
 * @Author yjwu            https://blog.csdn.net/qq_33576276/article/details/88786090
 * @Date 2020/4/8 11:20
 **/
@Aspect
@Component
@Slf4j
public class WebLogAop {
    @Autowired
    private RMQProducer rmqProducer;
    @Value("${logRMQExchangeName}")
    private String logRMQExchangeName;
    @Value("${logRMQRoutingKey}")
    private String logRMQRoutingKey;

    /**
     * @MethodName: logController
     * @Description: TODO //这个注解包含两部分，PointCut表达式和PointCut签名。
     * 表达式是拿来确定切入点的位置的，说白了就是通过一些规则来确定，哪些方法是要增强的，也就是要拦截哪些方法。
     * @Param: * @param
     * @Return:
     * @Author: yjwu
     * @Date: 2020/4/8 11:24
     **/
    @Pointcut("execution(public * com.youth.api..*.*(..))")
    public void operationExceptionLog() {

    }

    @Pointcut("@annotation(com.youth.aop.OperationLogAnnotation)")
    public void operationLog() {

    }


//    /**
//     * @param joinPoint 切入点
//     * @param keys      返回结果
//     * @MethodName: saveOperationLog
//     * @Description: TODO 正常返回拦截通知。正常操作完成后执行该拦截通知，如果连接点抛出异常，则不会通知。
//     * @Param:
//     * @Return:
//     * @Author: yjwu
//     * @Date: 2020/4/8 12:34
//     **/
//    @AfterReturning(value = "operationLog()", returning = "keys")
//    public void saveOperationLog(JoinPoint joinPoint, Object keys) {
//        //获取requestAttributes
//        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//        //从requestAttribute中获取htteservletrequest信息
//        HttpServletRequest httpServletRequest = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//        try {
//            LogEntity operationLog = new LogEntity();
//            operationLog.setId(CodeUtil.getUUIDCode());
//            //从切面织入处通过反射机制获取织入点处的方法
//            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//            //获取切入点所在的方法。
//            Method method = signature.getMethod();
//            OperationLogAnnotation annotation = method.getAnnotation(OperationLogAnnotation.class);
//            operationLog.setModual(annotation.operationModual());
//            operationLog.setType(annotation.operationType());
//            operationLog.setDesc(annotation.operationDesc());
//
//            //请求的类名
//            String className = joinPoint.getTarget().getClass().getName();
//            //请求的方法名
//            String methodName = method.getName();
//
//            operationLog.setMethod(className + "." + methodName);
//
//            //请求的参数
//            Map<String, String> requestParamMap = converMap(httpServletRequest.getParameterMap());
//            //将参数所在的数组转换json
//            String params = JSON.toJSONString(requestParamMap);
//            operationLog.setParams(params);
//            operationLog.setResponse(JSON.toJSONString(keys));
//            operationLog.setUserId("");
//            operationLog.setUserIp(IpUtil.getRemoteIp(httpServletRequest));
//            operationLog.setLocalIp(IpUtil.getLocalIp());
//            operationLog.setUri(httpServletRequest.getRequestURI());
//            operationLog.setCreateTime(new Date());
//            operationLog.setDoExLog(0);
//            operationLog.setExceptionMsg("");
//            operationLog.setExceptionName("");
//        } catch (Exception e) {
//            log.info(e.getMessage());
//        }
//
//    }


    /**
     * @param joinPoint 切入点
     * @param e         异常信息
     * @MethodName: saveExceptionLog
     * @Description: TODO  异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     * @Param:
     * @Return:
     * @Author: yjwu
     * @Date: 2020/4/8 12:52
     **/
    @AfterThrowing(pointcut = "operationExceptionLog()", throwing = "e")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        LogEntity excepLog = new LogEntity();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            excepLog.setDoExLog(1);
            excepLog.setId(CodeUtil.getUUIDCode());
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;
            // 请求的参数
            Map<String, String> rtnMap = converMap(request.getParameterMap());
            // 将参数所在的数组转换成json
            String params = JSON.toJSONString(rtnMap);
            // 请求参数
            excepLog.setParams(params);
            // 请求方法名
            excepLog.setMethod(methodName);
            // 异常名称
            excepLog.setExceptionName(e.getClass().getName());
            // 异常信息
            excepLog.setExceptionMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
            // 操作员ID
            excepLog.setUserId("");
            // 操作URI
            excepLog.setUri(request.getRequestURI());
            // 操作员IP
            excepLog.setUserIp(IpUtil.getRemoteIp(request));
            excepLog.setLocalIp(IpUtil.getLocalIp());
            excepLog.setCreateTime(new Date());
        } catch (Exception e1) {
            log.info(e1.getMessage());
        }

    }

    @Around(value = "operationLog()")
    public void saveSysLog(ProceedingJoinPoint proceedingJoinPoint) throws SocketException {
        ResultBean resultBean = new ResultBean();
        resultBean.setCode(200);
        resultBean.setMsg("环绕通知成功");
        resultBean.setData(null);

        //-------------环绕方法执行前---前置通知
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest httpServletRequest = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        LogEntity operationLog = new LogEntity();
        operationLog.setId(CodeUtil.getUUIDCode());
        //从切面织入处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        //获取切入点所在的方法。
        Method method = signature.getMethod();
        OperationLogAnnotation annotation = method.getAnnotation(OperationLogAnnotation.class);
        operationLog.setModual(annotation.operationModual());
        operationLog.setType(annotation.operationType());
        operationLog.setDesc(annotation.operationDesc());

        //请求的类名
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        //请求的方法名
        String methodName = method.getName();

        operationLog.setMethod(className + "." + methodName);

        //请求的参数
        Map<String, String> requestParamMap = converMap(httpServletRequest.getParameterMap());
        //将参数所在的数组转换json
        String params = JSON.toJSONString(requestParamMap);
        operationLog.setParams(params);

        operationLog.setUserId("");
        operationLog.setUserIp(IpUtil.getRemoteIp(httpServletRequest));
        operationLog.setLocalIp(IpUtil.getLocalIp());
        operationLog.setUri(httpServletRequest.getRequestURI());
        operationLog.setCreateTime(new Date());
        operationLog.setDoExLog(0);
        operationLog.setExceptionMsg("");
        operationLog.setExceptionName("");

        //开始调用时间
        // 计时并调用目标函数
        long start = System.currentTimeMillis();


        //调用service保存SysLog实体类到数据库
        //sysLogService.save(sysLog);
        try {
            //------方法执行时---
            Object result = proceedingJoinPoint.proceed();
            resultBean.setData(result);
            //方法执行后----
            operationLog.setResponse(JSON.toJSONString(result));
//            return resultBean;
        } catch (Throwable throwable) {
            //异常通知
            operationLog.setDoExLog(1);
            operationLog.setExceptionMsg(throwable.getMessage());
            operationLog.setExceptionName(throwable.getLocalizedMessage());
            throwable.printStackTrace();

            resultBean.setCode(400);
            resultBean.setMsg("环绕通知失败");
            resultBean.setData(null);
//            return resultBean;
        } finally {
            Long time = System.currentTimeMillis() - start;
            operationLog.setTotalTime(time);

            rmqProducer.send(JSON.toJSONString(operationLog), logRMQExchangeName, logRMQRoutingKey);
            //最终通知
            log.info("最终通知执行···");
        }
    }


    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        return message;
    }
}
